﻿using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.Text;

namespace Language.Syntax.Colouring
{
    internal class ClassificationResolver
    {
        #region Private Data

        private SortedList<int, ResolvedClassification> _classifications = new SortedList<int, ResolvedClassification>();
        private ClassificationTypes _classificationTypes;
        private Stub.Database _database;
        private string _relativeFilepath;

        #endregion Private Data

        #region Public Properties

        public SortedList<int, ResolvedClassification> Classifications => _classifications;

        #endregion Public Properties

        #region C-Tor

        public ClassificationResolver( string relativeFilepath, Stub.Database database, ClassificationTypes classificationTypes )
        {
            _relativeFilepath = relativeFilepath;
            _database = database;
            _classificationTypes = classificationTypes;
        }

        #endregion C-Tor

        #region Public Methods

        public void Scan( ITextSnapshot snapshot, IEnumerable< Stub.Token > tokens, IEnumerable< Stub.DetailedContext > stubs, IEnumerable< Stub.Literal > literals )
        {
            AddTokens( tokens, snapshot );
            AddStubs( snapshot, stubs );
            AddLiterals( snapshot, literals );
        }

        public void Scan( ITextSnapshot snapshot )
        {
            _classifications.Clear();

            Stub.File fileStub;
            if ( _database.FileByPath.TryGetValue( _relativeFilepath, out fileStub ) )
            {
                AddTokens( fileStub.Tokens.Values, snapshot );
                AddTokens( fileStub.SupplementaryTokens.Values, snapshot );
                AddStubs( snapshot, fileStub.Stubs.Values );
                AddLiterals( snapshot, fileStub.Literals.Values );
            }
        }

        #endregion Public Methods

        #region Private Methods

        private void Add( SnapshotSpan targetSpan, ClassificationType type )
        {
            ResolvedClassification classification = new ResolvedClassification()
            {
                Type = _classificationTypes[ type ],
                Span = targetSpan.Snapshot.CreateTrackingSpan( targetSpan.Span, SpanTrackingMode.EdgeInclusive )
            };

            _classifications[ targetSpan.End.Position ] = classification;
        }

        private void Add( Stub.Configuration configuration, SnapshotSpan targetSpan, ClassificationType native, ClassificationType scripted )
        {
            Add( targetSpan, configuration.IsNative ? native : scripted );
        }

        private void AddStub( Stub.Enum stub, SnapshotSpan targetSpan )
        {
            Add( stub.Configuration, targetSpan, ClassificationType.NativeEnum, ClassificationType.Enum );
        }

        private void AddStub( Stub.Option stub, SnapshotSpan targetSpan )
        {
            Add( stub.Parent.Configuration, targetSpan, ClassificationType.NativeEnumOption, ClassificationType.EnumOption );
        }

        private void AddStub( Stub.Class stub, SnapshotSpan targetSpan )
        {
            Add( stub.Configuration, targetSpan, ClassificationType.NativeClass, ClassificationType.Class );
        }

        private void AddStub( Stub.Function stub, SnapshotSpan targetSpan )
        {
            if ( stub.Configuration.IsOperator )
                return;

            if ( stub.Configuration.IsMember )
                Add( stub.Configuration, targetSpan, ClassificationType.NativeMethodCall, ClassificationType.MethodCall );
            else
                Add( stub.Configuration, targetSpan, ClassificationType.NativeFunctionCall, ClassificationType.FunctionCall );
        }

        private void AddStub( Stub.Property stub, SnapshotSpan targetSpan )
        {
            if ( stub.Configuration.IsLocal )
            {
                Add( targetSpan, ClassificationType.Local );
            }
            else if ( stub.Configuration.IsParameter )
            {
                Add( targetSpan, ClassificationType.Parameter );
            }
            else if ( stub.Configuration.IsMember )
            {
                //TODO: Native member
                Add( targetSpan, ClassificationType.Member );
            }
            else
            {
                //Probably shouldn't ever end up here
                Add( targetSpan, ClassificationType.Identifier );
            }
        }

        private void AddContext( Stub.DetailedContext context, ITextSnapshot snapshot )
        {
            Span span = new Span( context.Position.Start, context.Position.End - context.Position.Start );

            SnapshotSpan targetSnapshotSpan = new SnapshotSpan( snapshot, span );

            if ( context is Stub.Class )
            {
                Stub.Class stub = (Stub.Class)context;
                AddStub( stub, targetSnapshotSpan );
            }
            else if ( context is Stub.Property )
            {
                Stub.Property stub = (Stub.Property)context;
                AddStub( stub, targetSnapshotSpan );
            }
            else if ( context is Stub.Function )
            {
                Stub.Function stub = (Stub.Function)context;
                AddStub( stub, targetSnapshotSpan );
            }
            else if ( context is Stub.Comment )
            {
                Stub.Comment comment = (Stub.Comment)context;
                Add( targetSnapshotSpan, ClassificationType.Comment );
            }
            else if ( context is Stub.Enum )
            {
                Stub.Enum stub = (Stub.Enum)context;
                AddStub( stub, targetSnapshotSpan );
            }
            else if ( context is Stub.Option )
            {
                Stub.Option stub = (Stub.Option)context;
                AddStub( stub, targetSnapshotSpan );
            }
        }

        private void AddStubs( ITextSnapshot snapshot, IEnumerable< Stub.DetailedContext > stubs )
        {
            foreach ( Stub.DetailedContext context in stubs )
            {
                AddContext( context, snapshot );
            }
        }

        private void AddTokens( IEnumerable<Stub.Token> tokens, ITextSnapshot snapshot )
        {
            foreach ( Stub.Token token in tokens )
            {
                Span span = new Span( token.Position.Start, token.Position.End - token.Position.Start );
                SnapshotSpan snapshotSpan = new SnapshotSpan( snapshot, span );

                if ( token.Declaration is Stub.Property )
                {
                    Stub.Property stub = (Stub.Property)token.Declaration;
                    AddStub( stub, snapshotSpan );
                }
                else if ( token.Declaration is Stub.Function )
                {
                    Stub.Function stub = (Stub.Function)token.Declaration;
                    AddStub( stub, snapshotSpan );
                }
                else if ( token.Declaration is Stub.Enum )
                {
                    Stub.Enum stub = (Stub.Enum)token.Declaration;
                    AddStub( stub, snapshotSpan );
                }
                else if ( token.Declaration is Stub.Option )
                {
                    Stub.Option stub = (Stub.Option)token.Declaration;
                    AddStub( stub, snapshotSpan );
                }
                else if ( token.Declaration is Stub.Class )
                {
                    Stub.Class stub = (Stub.Class)token.Declaration;
                    AddStub( stub, snapshotSpan );
                }
            }
        }

        private void AddLiterals( ITextSnapshot snapshot, IEnumerable< Stub.Literal > literals )
        {
            foreach ( Stub.Literal literal in literals )
            {
                Span span = new Span( literal.Position.Start, literal.Position.End - literal.Position.Start );
                SnapshotSpan targetSpan = new SnapshotSpan( snapshot, span );

                ClassificationType type = ClassificationType.Count;

                switch ( literal.Type )
                {
                    case Stub.LiteralType.Int:
                        type = ClassificationType.LiteralInteger;
                        break;

                    case Stub.LiteralType.Float:
                        type = ClassificationType.LiteralFloat;
                        break;

                    case Stub.LiteralType.String:
                        type = ClassificationType.LiteralString;
                        break;

                    case Stub.LiteralType.Name:
                        type = ClassificationType.LiteralName;
                        break;

                    case Stub.LiteralType.Keyword:
                        type = ClassificationType.Keyword;
                        break;

                    case Stub.LiteralType.Integral:
                        type = ClassificationType.Integral;
                        break;
                }

                Debug.Assert( type != ClassificationType.Count );

                Add( targetSpan, type );
            }
        }

        #endregion Private Methods
    }
}
